#include <iostream>
#include <chrono>
#include <iomanip>

#include "json.hpp"
#include "BASEHACKREGION.h"
#include "yolov7.h"
#include "common.h"

struct Region
{
    BASEHACKREGION region;
    std::string name;
    int squeeze_threshold;
};

std::map<int, videoWriter> Init_All_Writer(std::map<int, Region> &regions)
{
    std::map<int, videoWriter> writers;
    for (auto it = regions.begin(); it != regions.end(); it++)
    {
        writers[it->first] = Init_Single_Writer(it->second.name);
    }
    return writers;
}

// 读取json文件中标注框的信息
std::map<int, Region> Json_To_Regions(std::string filename)
{
    std::ifstream f(filename);
    std::ostringstream tmp;
    tmp << f.rdbuf();
    std::string str = tmp.str();

    // std::cout << str << std::endl;
    nlohmann::json data = nlohmann::json::parse(str);

    std::vector<cv::Point> tem;
    std::map<int, Region> regions;

    for (int i = 0; i < data["regions"].size(); i++)
    {
        nlohmann::json points = data["regions"][i]["points"];
        nlohmann::json name = data["regions"][i]["name"];
        nlohmann::json squeeze_threshold = data["regions"][i]["squeeze_threshold"];
        std::vector<cv::Point> tem;

        for (int j = 0; j < points.size(); j++)
        {
            int x = points[j][0].template get<double>();
            int y = points[j][1].template get<double>();
            // std::cout << x << " " << y << std::endl;
            cv::Point point(x, y);
            tem.push_back(point);
        }
        BASEHACKREGION region = BASEHACKREGION(tem);
        // std::cout << name.get<std::string>() << std::endl;
        regions[regions.size()] = Region{region, name.get<std::string>(), squeeze_threshold.get<int>()};
    }
    return regions;
}

// 判别是否发生拥挤
void Car_Squeeze(std::vector<BoxInfo> &res, Region &region, int &cur_region_car_count)
{
    int squeeze_time = 10;            // 连续拥挤时间 秒
    int last_squeeze_alret = 10 * 60; // 上次报警时间 秒
    // 计算当前帧区域内车的数量
    for (size_t i = 0; i < res.size(); ++i)
    {
        // int label = res[i].label;  //获取检测出的种类
        // if (label != 3) continue;  //如果不是检测的种类，则跳过，这里跳过的是车
        int center_x = (res[i].x1 + res[i].x2) / 2;
        int center_y = (res[i].y1 + res[i].y2) / 2;
        cv::Point center = {center_x, center_y};
        if (region.region.detectHack(center))
        {
            cur_region_car_count++;
        }
    }
    // 如果发生了拥挤而且是第一次发生连续拥挤，则进行更新
    if (!region.region.region_last_squeeze_flag && cur_region_car_count > region.squeeze_threshold)
    {
        // std::cout << "发生更新" << std::endl;
        region.region.region_start_alert_time = Get_Current_Timestamp();
        region.region.region_last_squeeze_flag = true;
    }
    // 如果拥挤不连续，则标记为false
    else if (cur_region_car_count <= region.squeeze_threshold)
    {
        region.region.region_last_squeeze_flag = false;
    }
    auto now_time = Get_Current_Timestamp();                                                 // 获取当前时间戳
    int region_squeeze_diff = int(now_time - region.region.region_start_alert_time) / 1000;  // 当前时间戳与连续拥挤的第一次时间戳相减 单位：秒
    int region_alret_diff = int(now_time - region.region.region_last_sendalret_time) / 1000; // 当前时间戳与上一次报警时间戳相减 单位：秒
    // std::cout << "当前区域连续拥挤时间" << region_squeeze_diff << std::endl;
    // 如果发生了连续拥挤，且拥挤的时间大于squeeze_time， 且距离上次报警的时间大于last_squeeze_alret分钟，则发送报警信号
    if (region.region.region_last_squeeze_flag && region_squeeze_diff > squeeze_time && region_alret_diff > last_squeeze_alret)
    {
        // std::cout << "当前区域发生拥挤！！！" << std::endl;
        region.region.sendflag = true;
        region.region.region_last_sendalret_time = now_time;
        region.region.region_last_squeeze_flag = false;
    }
    else
    {
        region.region.sendflag = false;
    }
}

void Store_Before_AlertFrame(videoWriter &writer, TimestampQueue &img_deque, Region &region, int time_threshold)
{
    std::string output_string = writer.name + "发生车辆拥挤";
    long long now_time = Get_Current_Timestamp();
    for (auto queue_it = img_deque.img_timestamp_deque.begin(); queue_it != img_deque.img_timestamp_deque.end(); queue_it++)
    {
        int before_img_diff = (now_time - queue_it->timestamp) / 1000;
        cv::Mat before_img = queue_it->image.clone();
        cv::putText(before_img, output_string, cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 0.75, CV_RGB(255, 0, 0), 1);
        if (int((before_img_diff % 10)) % 2)
            region.region.drawRegion(before_img);
        writer.alert_video.write(before_img);
        if (before_img_diff > time_threshold)
        {
            break;
        }
    }
    writer.is_first_frame = false;
}

void Store_After_AlertFrame(cv::Mat src, videoWriter &writer, Region &region)
{
    std::string output_string = writer.name + "发生车辆拥挤";
    int alert_time = (Get_Current_Timestamp() - writer.start_timestamp) / 1000;
    cv::Mat alert_src = src.clone();
    cv::putText(alert_src, output_string, cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 0.75, CV_RGB(255, 0, 0), 1);
    if (int((alert_time % 10)) % 2)
    { // 做出边界框闪动效果
        region.region.drawRegion(alert_src);
        if (writer.alert_img.empty())
        {
            writer.alert_img = alert_src.clone();
        }
    }
    writer.alert_video.write(alert_src);
}

int main()
{
    //  创建变量集合
    //  读取网络模型
    Net_config YOLOV7_nets = {0.5, 0.5, "static/models/best_5.onnx"};
    YOLOV7 net(YOLOV7_nets, "YOLOV7");
    // 读取视频
    cv::VideoCapture cap("static/videos/cam0_11_28_rotate.mp4");
    cv::Mat src;
    cap >> src;
    if (src.empty())
    {
        std::cout << "读入视频失败" << std::endl;
        return 0;
    }
    else
    {
        std::cout << "读入视频成功" << std::endl;
    }
    // 读入入侵区域
    std::map<int, Region> regions = Json_To_Regions("static/json/cicheng_illegal_parking/cam0.json");
    TimestampQueue img_deque{500};
    // 创建写入视频类 根据区域初始化writers
    srand(time(nullptr));
    std::map<int, videoWriter> writers = Init_All_Writer(regions);
    while (!src.empty())
    {
        img_deque.Push_Back(src.clone());
        std::vector<BoxInfo> res = net.detect(src);
        //  画yolo检测框
        for (int i = 0; i < res.size(); i++)
        {
            std::string class_name = net.class_names[res[i].label];
            std::string label = class_name;
            cv::rectangle(src, cv::Point(int(res[i].x1), int(res[i].y1)), cv::Point(int(res[i].x2), int(res[i].y2)), cv::Scalar(0, 0, 255), 2);
            cv::putText(src, label, cv::Point(int(res[i].x1), int(res[i].y1) - 5), cv::FONT_HERSHEY_SIMPLEX, 0.75, cv::Scalar(0, 255, 0), 1);
        }
        int cur_region_car_count = 0; // 当前帧车的数量
        for (auto it = regions.begin(); it != regions.end(); it++)
        {
            Car_Squeeze(res, it->second, cur_region_car_count);
            if (it->second.region.sendflag)
            {
                std::cout << "发生报警" << std::endl;
                writers[it->first].is_begin_store = true;
                writers[it->first].is_first_frame = true;
                writers[it->first].start_timestamp = Get_Current_Timestamp();
                writers[it->first].alert_video.open(writers[it->first].outfile_video_path, cv::VideoWriter::fourcc('X', 'V', 'I', 'D'), 10, cv::Size(src.cols, src.rows));
            }
        }

        // 遍历writers
        for (auto it = writers.begin(); it != writers.end(); it++)
        {

            if (it->second.is_first_frame)
                Store_Before_AlertFrame(it->second, img_deque, regions[it->first], 45); // writer, 队列，哪个区域，显示在左上角的字，队列中存储多少秒前的图片
            if (it->second.is_begin_store)
                Store_After_AlertFrame(src, it->second, regions[it->first]); // 区域从这帧开始报警
            else
                continue; // 区域未报警
            std::cout << (Get_Current_Timestamp() - it->second.start_timestamp) / 1000 << std::endl;
            if (((Get_Current_Timestamp() - it->second.start_timestamp) / 1000) > 30)
            { // 存储视频大于60s，结束存储，并发送
                End_Writer(it->second);
                Send_Message(it->second);
                writers[it->first] = Init_Single_Writer(it->second.name);
            }
        }
        cv::namedWindow("region in image", cv::WINDOW_NORMAL);
        cv::imshow("region in image", src);
        cv::waitKey(50);
        cap >> src;
    }
    cap.release();
    return 0;
}
